package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"

	_ "github.com/go-sql-driver/mysql"
)

func main() {
	rawPath := flag.String("config", "", "")
	flag.Parse()
	configPath := strings.Trim(*rawPath, "")
	if len(configPath) == 0 {
		fmt.Printf("%s --config <config file>\n", os.Args[0])
		os.Exit(-1)
	}
	cfg := loadConfig(configPath)
	log.WithFields(log.Fields{"config": cfg}).Info("loading config")

	if cfg.MaxLines <= 0 {
		cfg.MaxLines = 10
	}

	server, err := NewServer(cfg.ServerConfig)
	if err != nil {
		log.WithFields(log.Fields{"err": err, "config": cfg}).Fatal("failed create query server")
	}

	req, err := NewRequester(cfg.RequesterConfig)
	if err != nil {
		log.WithFields(log.Fields{"err": err, "config": cfg}).Fatal("failed create requester")
	}

	lastHistory := time.Second * 70
	if strings.TrimSpace(cfg.LastHistory) != "" {
		lastHistory, err = time.ParseDuration(cfg.LastHistory)
		if err != nil {
			log.WithFields(log.Fields{"err": err, "config": cfg}).Fatal("failed parse lastHistory duration")
		}
	}
	now := time.Now()
	ceil := now.Add(lastHistory * -1)
	if cfg.StartTime.IsZero() {
		fmt.Printf("cfg = iszero\n")
		cfg.StartTime = ceil
	}

	for idx := range cfg.ShipConfigs {
		ship := &cfg.ShipConfigs[idx]
		if ship.StartTime.IsZero() {
			ship.StartTime = cfg.StartTime
		}
	}

	sph, err := NewSavePointHandle(cfg.SavePointFile)
	if err != nil {
		log.WithFields(log.Fields{"err": err, "config": cfg}).Fatal("failed load save point file")
		panic(err)
	}
	for _, ship := range cfg.ShipConfigs {
		startTime := sph.GetStartTime(ship.MMSI)
		if startTime == nil || startTime.IsZero() {
			startTime = &ship.StartTime
			err := sph.UpdateStartTime(ship.MMSI, *startTime)
			if err != nil {
				log.WithFields(log.Fields{"err": err, "savePoint": sph.savePoint}).Error("initialize save point failed")
			}
		}
		err := sph.Persist()
		if err != nil {
			log.WithFields(log.Fields{"err": err}).Fatal("failed persist save point on initialize")
		}
	}

	tick := 0
	for {
		log.WithFields(log.Fields{"tick": tick}).Info("start")
		allData := []ShipData{}
		for _, ship := range cfg.ShipConfigs {
			startTime := sph.GetStartTime(ship.MMSI)
			data, err := server.GetLatestDataByMMSI(ship.MMSI, *startTime)
			if err != nil {
				log.WithFields(log.Fields{"err": err, "ship": ship}).Error("failed get ship data")
			}
			allData = append(allData, data...)
		}
		if len(allData) == 0 {
			log.WithFields(log.Fields{"tick": tick}).Info("no new data to send")
		} else {
			err := sendData(req, allData, sph, cfg.MaxLines)
			if err != nil {
				log.WithFields(log.Fields{"err": err}).Error("failed sending data")
			} else {
				log.WithFields(log.Fields{}).Info("successfully sending data")
			}
		}
		tick = tick + 1
		time.Sleep(time.Minute * 1)
	}
}

func sendData(req *Requester, data []ShipData, sph *SavePointHandle, maxLines int) error {
	if maxLines <= 0 {
		maxLines = 10
	}
	slices := sliceData(data, maxLines)
	for _, slice := range slices {
		err := sendSlice(req, slice, sph)
		if err != nil {
			return err
		}
	}
	return nil
}

func sendSlice(req *Requester, data []ShipData, sph *SavePointHandle) error {
	payload := formatData(req, data)
	err := sendPayload(req, payload)
	if err != nil {
		return err
	}
	for _, d := range data {
		_ = sph.UpdateStartTime(d.MMSI, d.Time)
	}
	return sph.Persist()
}

func formatData(req *Requester, data []ShipData) string {
	stmpl := `<string>%d,%s,%f,%f,%s,%s,%s,%s,%s,%f</string>`
	lines := []string{}
	for _, d := range data {
		t := formatTime(d.Time)
		line := fmt.Sprintf(stmpl, d.MMSI, d.Name, d.Longitude, d.Latitude, "", t, "", "ESHIPENET", "N", d.Speed)
		lines = append(lines, line)
	}
	allLine := strings.Join(lines, "\n")
	payload := fmt.Sprintf(req.config.Template, allLine)
	return payload
}

func formatTime(t time.Time) string {
	return t.Format("2006-01-02 15:04:05")
}

func sendPayload(requester *Requester, payload string) error {
	return requester.Request([]byte(payload))
}

func sliceData(data []ShipData, count int) [][]ShipData {
	ds := [][]ShipData{}
	total := len(data)
	slices := total / count
	for i := 0; i < slices; i++ {
		d := data[i*count : (i+1)*count]
		ds = append(ds, d)
	}
	if total%count != 0 {
		ds = append(ds, data[slices*count:])
	}
	return ds
}

func loadConfig(path string) Config {
	configFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer configFile.Close() // nolint
	content, err := ioutil.ReadAll(configFile)
	if err != nil {
		panic(err)
	}
	var config Config
	err = json.Unmarshal(content, &config)
	if err != nil {
		panic(err)
	}
	return config
}
